/*******************************************************************************************
Copyright (C) 2019 Mestrelab Research S.L. All rights reserved.

This file is part of the Mnova scripting toolkit.

Authorized users of MNova Software may use this file freely, but this file is provided AS IS
with NO WARRANTY OF ANY KIND, INCLUDING THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE.
*******************************************************************************************/

/*global WMultipletSimulatorDialog, nmr, Settings, Application, Dir, FileDialog, File, MnUi, TextStream, qsTr, MultipletReporterParser, print, mainWindow, serialization, molecule, MessageBox, Document*/
/*jslint plusplus: true, indent: 4*/

/*************************************************************************************************/
//!
function multipletReportParser() {
	'use strict';
	try {
		Application.checkDependencies(["NMR"]);
	} catch (ERR) {
		MessageBox.critical(ERR);
		return;
	}

	var displayDialog = new WMultipletSimulatorDialog();
	if (displayDialog.exec()) {
		try {
			nmr.syntheticMultipletsSpectrum(displayDialog.fReportParser.toXML());
		} catch (ERR) {
			MessageBox.critical(ERR);
		}
	}
}

//!
function multipletReport2XML(aReport, aOutputFile) {
	'use strict';
	var parser = new MultipletReporterParser(),
		xml,
		file,
		txtStrm;
	if (aReport) {
		parser.parseReport(aReport);
		if (parser.isValid()) {
			xml = parser.toXML();
			if (xml) {
				file = new File(aOutputFile);
				if (file.open(File.WriteOnly)) {
					txtStrm = new TextStream(file);
					txtStrm.write(xml);
					file.close();
					return true;
				}
				print(qsTr("Unable to open file"));
			} else {
				print(qsTr("Invalid XML"));
			}
		} else {
			print(qsTr("Invalid Report"));
		}
	}
	return false;
}

/*************************************************************************************************/
//!
function MultipletReporterParser() {
	'use strict';
	this.fUsePrediction = false;
	this.clear();
	this.readSettings();
}

//!
MultipletReporterParser.prototype = {
	//!
	clear : function () {
		'use strict';
		this.fShiftAndJs = [];
		this.fPoints = 1024 * 64;
		this.fMHz = 500;
		this.fNucleus = '';
		this.fSolvent = 'CDCl3';
		this.fFrom = -2.0;
		this.fTo = 15.0;
		this.fMolfile = '';
		this.fDefaultLineWidth = 0.75; //Hz
		this.fOriginalReport = '';
		this.warnings = {};
		this.autocalculated = false;
	},

	//!
	isValid : function () {
		'use strict';
		return this.fShiftAndJs && this.fShiftAndJs.length && this.fNucleus;
	},

	//!
	writeSettings : function () {
		'use strict';
		var setts, key;
		if (this.fNucleus) {
			key = this.fNucleus.replace(/\d+/, '');
			setts = new Settings(MultipletReporterParser.settingsKey);
			setts.setValue(key, JSON.stringify({'pointNumber': this.fPoints, 'from': this.fFrom, 'to': this.fTo, 'usePrediction': this.fUsePrediction}));
		}
	},

	//!
	readSettings : function () {
		'use strict';
		var key, setts, jsonString, jVal;

		key = this.fNucleus.replace(/\d+/, '');
		setts = new Settings(MultipletReporterParser.settingsKey);
		if (key && setts.contains(key)) {
			jsonString = setts.value(key, JSON.stringify({'pointNumber': this.fPoints, 'from': this.fFrom, 'to': this.fTo, 'usePrediction': this.fUsePrediction}));
			if (jsonString.length) {
				jVal = JSON.parse(jsonString);
				if (jVal.pointNumber) {
					this.fPoints = jVal.pointNumber;
				}
				if (jVal.from !== undefined) {
					this.fFrom = jVal.from;
				}
				if (jVal.to !== undefined) {
					this.fTo = jVal.to;
				}
				if (jVal.usePrediction !== undefined) {
					this.fUsePrediction = jVal.usePrediction;
				}
			}
		} else {
			this.nucleusToRange();
		}
	},

	//!
	parseReport : function (aReport) {
		'use strict';
		/*jslint regexp: true*/
		var MHzRE, begRE, nucRE, solvRE, shiftRE, jListRE, floatingPointRE, nNRE, classRE, /*atomNumberRE,*/ reMatch, nucArray, solvArray, multfrom, multto,
			jListMatch, jListStr, multClassMatch, nNMatch, obj, floatReStr, shiftReStr, jListReStr, nNucFloat,
			protonLeftOutliers = [], protonRightOutliers = [],
			carbonLeftOutliers = [], carbonRightOutliers = [],
			fluorineLeftOutliers = [], fluorineRightOutliers = [],
			phosphorusLeftOutliers = [], phosphorusRightOutliers = [];

		aReport = aReport.toUtf8();
		aReport = aReport.replace(/\sand\s/g, ','); //some reports use "and" to separate the last multiplet
		aReport = aReport.replace(/[\n\r]/g, ''); //removes all newlines
		aReport = aReport.replace(/[^0-9\)]+$/g, ''); //ends with a ) or a number
		aReport = aReport.replace(/[\u002D\u2010\u2011\u2012\u2013\u2014\u2015\u058A\u05BE\u207B\u208B\u2212\u2E3A\u2E3B\u30A0\uFE58\uFE63\uFF0D]/g, "-"); //replace all unicode dashes with hyphen
		this.clear();
		this.warnings = {};
		this.fOriginalReport = aReport;

		floatReStr = '([+\\-]?\\d+(?:\\.\\d+)?)';
		shiftReStr = '(?:(%1\\s*[\\-]\\s*%2)|%3)\\s*(?:\\((.*?)\\)(?:(?:\\s*,\\s*)|$))?'.arg(floatReStr).arg(floatReStr).arg(floatReStr);
		jListReStr = 'J\\w?\\s*=?\\s*((?:(?:,?\\s*%1(?![A-Za-z]))\\s*)+)\\s*(?:Hz)?'.arg(floatReStr); //J = 1.2, 2.3 Hz : true; J = 1.2, 2H : false
		MHzRE = new RegExp('%1\\s*MHz'.arg(floatReStr), 'i');
		begRE = /^(.*?)\s*\((.*?)\)/;
		//nucRE = /\d{0,2}[HCPF][a-z]*/;
		nucRE = /\d{0,2}(H|C|P|F|B|O|Si|N)\d{0,2}/;
		solvRE = /[a-zA-Z\-0-9]+/;

		//matches shiftRange|shift(j info)
		shiftRE = new RegExp(shiftReStr, 'g');

		//matches J=(jlist)
		jListRE = new RegExp(jListReStr);
		floatingPointRE = new RegExp(floatReStr, 'g');

		//matches 1H
		nNRE = /(\d+(?:\.\d+)?)\s?(H|C|P|F|B|O|Si|N)(?:\s|$|,|;)/;
		classRE = /(?:^|[^\w])((?:br?\s?)?(?:[msdtqph]|hept|br|[td]+[tdq]))(?:$|[^\w])/;
		//matches H1a, 1', 1b-H
		//atomNumberRE = /(?:H)?(\d+(?:'{1,3}|[abc])?)(?:-H)?/g;

		reMatch = MHzRE.exec(aReport); //something with MHz

		if (reMatch !== null) {
			this.fMHz = reMatch[1];
			aReport = aReport.replace(MHzRE, '');
		} else {
			this.warnings.frecuency = "Not able to get frequency from the report. Using Default: " + this.fMHz + "MHz";
		}

		reMatch = begRE.exec(aReport); //everything at the beginning

		if (reMatch !== null) {
			nucArray = reMatch[1].match(nucRE);

			if (nucArray !== null) {
				this.fNucleus = nucArray[0].replace(/^\s+|\s+$/, '');
				reMatch[1] = reMatch[1].replace(nucRE, '');
			} else {
				this.warnings.nucleus = true;
			}

			solvArray = reMatch[2].match(solvRE);

			if (solvArray !== null) {
				this.fSolvent = solvArray[0].replace(/^\s+|\s+$/, '');
				reMatch[2] = reMatch[2].replace(solvRE, '');
			} else {
				this.warnings.solvent = "Not able to get solvent from the report. Using Default: " + this.fSolvent;
			}
			aReport = aReport.replace(begRE, '');
		}

		this.readSettings();
		reMatch = shiftRE.exec(aReport);

		while (reMatch !== null) {
			obj = {'shift': undefined, 'range': undefined, 'integral': 1.0, 'nN': 1, 'jList': [], 'mClass': 'm', 'atomNumber': undefined, 'width': undefined};

			if (reMatch[1] !== undefined) { //its a multiplet
				multfrom = parseFloat(reMatch[2]);
				multto = parseFloat(reMatch[3]);

				if (isNaN(multto)) {
					obj.shift = [multfrom];
					obj.width = this.fDefaultLineWidth;
				} else {
					obj.range = {'from': multfrom, 'to': multto};
					obj.shift = [((multfrom + multto) * 0.5).toFixed(3)];
					obj.width = Math.abs(multto - multfrom) * this.fMHz / 4.0;
				}

				obj.jList = [];
				obj.mClass = 'm';

				if (reMatch[5]) {
					reMatch[5] = reMatch[5].replace(classRE, '');
				}

			} else if (reMatch[4] !== undefined) {
				obj.shift = [reMatch[4]];
				obj.width = this.fDefaultLineWidth;
				obj.jList = null;
				jListMatch = jListRE.exec(reMatch[5]);

				if (jListMatch !== null) {
					jListStr = jListMatch[1];
					obj.jList = jListStr.match(floatingPointRE);
					reMatch[5] = reMatch[5].replace(jListRE, '');
				}

				multClassMatch = classRE.exec(reMatch[5]);

				if (multClassMatch !== null) {
					obj.mClass = multClassMatch[1];
					reMatch[5] = reMatch[5].replace(classRE, '');

					if (obj.jList !== null) {
						obj.jList = MultipletReporterParser.expandJList(obj.mClass, obj.jList);
					}

				} else if (jListMatch === null && this.fNucleus.search(/1?H1?/) === -1) {
					obj.mClass = 's';
				}
			}
			nNMatch = nNRE.exec(reMatch[5]);
			if (nNMatch !== null) {
				nNucFloat = parseFloat(nNMatch[1]);
				if (nNucFloat <= 0) {
					nNucFloat = 1;
				}
				obj.integral = nNucFloat;
				obj.nN = nNucFloat.toFixed().toString();
				reMatch[5] = reMatch[5].replace(nNRE, '');
			}

			if (reMatch[5]) { //add the rest to atom number that acts as label
				obj.atomNumber = reMatch[5].replace(/^[\s,]*/, '').replace(/[\s,]*$/, '');
				obj.atomNumber = obj.atomNumber.replace(/\u2019/g, "'");
			} else {
				obj.atomNumber = '';
			}
			this.fShiftAndJs.push(obj);

			if (Number(obj.shift) > 15) {
				protonRightOutliers.push(obj.shift);
			}
			if (Number(obj.shift) < -2) {
				protonLeftOutliers.push(obj.shift);
			}
			if (Number(obj.shift) > 230) {
				carbonRightOutliers.push(obj.shift);
			}
			if (Number(obj.shift) < -20) {
				carbonLeftOutliers.push(obj.shift);
			}
			if (Number(obj.shift) > 500) {
				phosphorusRightOutliers.push(obj.shift);
			}
			if (Number(obj.shift) < -200) {
				phosphorusLeftOutliers.push(obj.shift);
			}
			if (Number(obj.shift) > 700) {
				fluorineRightOutliers.push(obj.shift);
			}
			if (Number(obj.shift) < -330) {
				fluorineLeftOutliers.push(obj.shift);
			}
			reMatch = shiftRE.exec(aReport);
		}

		this.rangeToNucleus(protonLeftOutliers, protonRightOutliers, carbonLeftOutliers, carbonRightOutliers, phosphorusLeftOutliers, phosphorusRightOutliers, fluorineLeftOutliers, fluorineRightOutliers, aReport);
		this.nucleusToRange(); //After detecting the nucleus let's put the right range in the dialog
	},

	rangeToNucleus : function (aProtonLeftOutliers, aProtonRightOutliers, aCarbonLeftOutliers, aCarbonRightOutliers, aPhosphorusLeftOutliers, aPhosphorusRightOutliers, aFluorineLeftOutliers, aFluorineRightOutliers, aReport) {
		'use strict';


		var pattProton = new RegExp("[0-9]*\\s*H"),
			pattCarbon = new RegExp("[0-9]*\\s*C"),
			pattPhosphorus = new RegExp("[0-9]*\\s*P"),
			pattFluorine = new RegExp("[0-9]*\\s*F");


		this.outliers = [];

		if (this.warnings.nucleus) {
			if (pattProton.test(aReport)) {
				this.fNucleus = "1H";
				this.warnings.nucleus = "Nucleus derived from data in report: 1H";
				this.autocalculated = true;
			} else if (pattCarbon.test(aReport)) {
				this.fNucleus = "13C";
				this.warnings.nucleus = "Nucleus derived from data in report: 13C";
				this.autocalculated = true;
			} else if (pattPhosphorus.test(aReport)) {
				this.fNucleus = "31P";
				this.warnings.nucleus = "Nucleus derived from data in report: 31P";
				this.autocalculated = true;
			} else if (pattFluorine.test(aReport)) {
				this.fNucleus = "19F";
				this.warnings.nucleus = "Nucleus derived from data in report: 19F";
				this.autocalculated = true;
			} else if (aProtonLeftOutliers.length < 2 && aProtonRightOutliers.length < 2) {
				this.fNucleus = "1H";
				this.autocalculated = true;
				this.warnings.nucleus = "Nucleus not found. It has been derived from ranges in data: 1H";

			} else if (aCarbonLeftOutliers.length < 2 && aCarbonRightOutliers.length < 2) {
				this.fNucleus = "13C";
				this.autocalculated = true;
				this.warnings.nucleus = "Nucleus not found. It has been derived from ranges in data: 13C";

			} else if (aPhosphorusLeftOutliers.length < 2 && aPhosphorusRightOutliers.length < 2) {
				this.fNucleus = "31P";
				this.autocalculated = true;
				this.warnings.nucleus = "Nucleus not found. It has been derived from ranges in data: 31P";

			} else if (aFluorineLeftOutliers.length < 2 && aFluorineRightOutliers.length < 2) {
				this.fNucleus = "19F";
				this.autocalculated = true;
				this.warnings.nucleus = "Nucleus not found. It has been derived from ranges in data: 19F";

			} else {
				this.fNucleus = "1H";
				this.autocalculated = false;
				this.warnings.nucleus = "Nucleus not found. Default nucleus used: 1H";
			}
		}

		if ((this.fNucleus === "1H" && this.autocalculated) && (aProtonLeftOutliers.length || aProtonRightOutliers.length)) {
			this.outliers = aProtonLeftOutliers.concat(aProtonRightOutliers);
			this.warnings.outliers = "Possible outliers found: [" + this.outliers + "]";
		}
		if ((this.fNucleus === "13C" && this.autocalculated) && (aCarbonLeftOutliers.length || aCarbonRightOutliers.length)) {
			this.outliers = aCarbonLeftOutliers.concat(aCarbonRightOutliers);
			this.warnings.outliers = "Possible outliers found: [" + this.outliers + "]";
		}
		if ((this.fNucleus === "31P" && this.autocalculated) && (aPhosphorusLeftOutliers.length || aPhosphorusRightOutliers.length)) {
			this.outliers = aPhosphorusLeftOutliers.concat(aPhosphorusRightOutliers);
			this.warnings.outliers = "Possible outliers found: [" + this.outliers + "]";
		}
		if ((this.fNucleus === "19F" && this.autocalculated) && (aFluorineLeftOutliers.length || aFluorineRightOutliers.length)) {
			this.outliers = aFluorineLeftOutliers.concat(aFluorineRightOutliers);


			this.warnings.outliers = "Possible outliers found: [" + this.outliers + "]";
		}
	},


	foundSecondMinOutlier: function () {
		"use strict";

		var tempArray, newMin, array = this.fShiftAndJs.map(function (o) {return parseFloat(o.shift); }),
			min = Math.min.apply(Math, array);
		tempArray = array.slice(0);
		tempArray.splice(tempArray.indexOf(min), 1);
		newMin = Math.min.apply(Math, tempArray);
		if (newMin < -2) {
			return true;
		}
		return false;
	},

	foundSecondMaxOutlier: function () {
		"use strict";

		var tempArray, newMax, array = this.fShiftAndJs.map(function (o) {return parseFloat(o.shift); }),
			max = Math.max.apply(Math, array);

		tempArray = array.slice(0);
		tempArray.splice(tempArray.indexOf(max), 1);
		newMax = Math.max.apply(Math, tempArray);
		if (newMax > 15) {
			return true;
		}
		return false;
	},

	getMean: function (aArray) {
		"use strict";

		var sum = 0, i;

		for (i = 0; i < aArray.length; i++) {
			sum += aArray[i];
		}

		return aArray.length ? sum / aArray.length : 0;
	},

	getSD: function (aArray, aMean) {
		"use strict";
		var i, sum = 0;

		for (i = 0; i < aArray.length; i++) {
			sum += Math.pow(aArray[i] - aMean, 2);
		}

		return Math.pow(sum / aArray.length, 0.5);
	},

	nucleusToRange : function () {
		'use strict';
		if (this.fNucleus.search(/1?H1?/) !== -1) {
			this.fFrom = -2.0;
			this.fTo = 15.0;
			this.fDefaultLineWidth = 0.75;
		} else if (this.fNucleus.search(/\s*(13)?C(13)?\s*/) !== -1) {
			this.fFrom = -20.0;
			this.fTo = 230.0;
			this.fDefaultLineWidth = 1.5;
		} else if (this.fNucleus.search(/\s*(19)?F(19)?\s*/) !== -1) {
			this.fFrom = -250.0;
			this.fTo = 50.0;
			this.fDefaultLineWidth = 1.5;
		} else if (this.fNucleus.search(/\s*(31)?P(31)?\s*/) !== -1) {
			this.fFrom = -500.0;
			this.fTo = 500.0;
			this.fDefaultLineWidth = 1.5;
		} else if (this.fNucleus.search(/\s*(11)?B(11)?\s*/) !== -1) {
			this.fFrom = -60.0;
			this.fTo = 100.0;
			this.fDefaultLineWidth = 1.5;
		} else if (this.fNucleus.search(/\s*(15)?N(15)?\s*/) !== -1) {
			this.fFrom = -500.0;
			this.fTo = 500.0;
			this.fDefaultLineWidth = 1.5;
		} else if (this.fNucleus.search(/\s*(17)?O(17)?\s*/) !== -1) {
			this.fFrom = -50.0;
			this.fTo = 850.0;
			this.fDefaultLineWidth = 1.5;
		} else if (this.fNucleus.search(/\s*(29)?Si(29)?\s*/) !== -1) {
			this.fFrom = -200.0;
			this.fTo = 850.0;
			this.fDefaultLineWidth = 1.5;
		}
	},

	//!
	toXML : function () {
		'use strict';
		/*jslint regexp: true*/
		var xml, multipletXml, multipletList, i, j, jString, widthStr, molfileElem;

		xml = '<mnova-multiplets version="1.0"  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="Mnova-Multiplet.xsd">\n<from>%1</from>\n<to>%2</to>\n<frequency>%3</frequency>\n<solvent>%4</solvent>\n<nucleus>%5</nucleus>\n<nPoints>%6</nPoints>\n<multipletList>%7</multipletList>\n%8</mnova-multiplets>\n<!-- __ORIGINAL__ -->\n'.arg(this.fFrom).arg(this.fTo).arg(this.fMHz).arg(this.fSolvent).arg(this.fNucleus).arg(this.fPoints);
		xml = xml.replace('__ORIGINAL__', this.fOriginalReport);
		multipletXml = '<multiplet>\n<numberOfNuclides>%1</numberOfNuclides>\n<shift>%2</shift>\n<class>%3</class>\n<jList>%4\n</jList>\n<atomNumber><![CDATA[%5]]></atomNumber>\n%6</multiplet>\n';
		multipletList = '';
		for (i = 0; i < this.fShiftAndJs.length; ++i) {
			if (this.fShiftAndJs[i].shift.length === 1) {
				jString = '';
				if (this.fShiftAndJs[i].jList) {
					for (j = 0; j < this.fShiftAndJs[i].jList.length; ++j) {
						jString += '\n<j>%1</j>'.arg(this.fShiftAndJs[i].jList[j]);
					}
				}
				widthStr = '';
				if (this.fShiftAndJs[i].width !== undefined) {
					widthStr = '<width>%1</width>\n'.arg(this.fShiftAndJs[i].width);
				}
				multipletList += multipletXml.arg(this.fShiftAndJs[i].nN).arg(this.fShiftAndJs[i].shift[0]).arg(this.fShiftAndJs[i].mClass).arg(jString).arg(this.fShiftAndJs[i].atomNumber).arg(widthStr).replace(/<(.*)><\/\1>/g, '');
			}
		}
		molfileElem = '';
		if (this.fMolfile !== undefined && this.fMolfile.length !== 0) {
			molfileElem = '<molfile usePrediction="%1">\n<![CDATA[%2]]>\n</molfile>\n'.arg(this.fUsePrediction).arg(this.fMolfile);
			molfileElem = molfileElem.replace(/[\u2010\u2011\u2012\u2013\u2014\u2015]/g, "-"); //replace all unicode dashes with hyphen
		}

		return xml.arg(multipletList).arg(molfileElem).replace(/(\s)*<(.*)><\/\1>(\s)*/g, '');
	}
};

//!
MultipletReporterParser.settingsKey = 'NMR/MultipletReporterParser';


//!
MultipletReporterParser.JListSizeExpected = {'d': 1, 't': 2, 'q': 3, 'p': 4, 'hexaplet': 5, 'h': 5, 'hex': 5, 'hept': 6};

//!
MultipletReporterParser.expectedJNumber = function (aType) {
	'use strict';
	var siz, re, reMatch, i;

	siz = 0;
	re = /hexaplet|hex|hept|[dtqph]/gi;
	reMatch = aType.match(re);
	if (reMatch !== null) {
		for (i = 0; i < reMatch.length; ++i) {
			if (MultipletReporterParser.JListSizeExpected.hasOwnProperty(reMatch[i])) {
				siz += MultipletReporterParser.JListSizeExpected[reMatch[i]];
			}
		}
	}
	return siz;
};

//!
MultipletReporterParser.expandJList = function (aType, aJList) {
	'use strict';
	var expSiz, expandedJList, re, reMatch, i, j, siz, p;

	expSiz = MultipletReporterParser.expectedJNumber(aType);
	if (aJList.length < expSiz) {
		//js descending
		aJList = aJList.sort(function (a, b) { return b - a; });
		expandedJList = [];
		re = /hexaplet|hex|hept|[dtqph]/gi;
		reMatch = aType.match(re);
		if (reMatch !== null) {
			for (i = 0; i < reMatch.length && aJList.length !== 0; ++i) {
				if (MultipletReporterParser.JListSizeExpected.hasOwnProperty(reMatch[i])) {
					j = aJList.shift();
					siz = MultipletReporterParser.JListSizeExpected[reMatch[i]];
					for (p = 0; p < siz; ++p) {
						expandedJList.push(j);
					}
				} else {
					return aJList;
				}
			}
			return expandedJList;
		}
	}
	return aJList;
};


/*************************************************************************************************/
//!
function WMultipletSimulatorDialog() {
	'use strict';

	var powerOf2Items, i;
	this.ui = Application.loadUiFile("ricares:multipletReportParser.ui");
	powerOf2Items = [];
	for (i = 1; i < 20; ++i) {
		powerOf2Items.push(Math.pow(2, i).toString());
	}
	this.ui.widgets.fPointComboBox.items = powerOf2Items;
	this.ui.widgets.fReportPlainTextEdit.textChanged.connect(this, 'onReportChanged');
	this.ui.widgets.fMolefileLineEdit.textChanged.connect(this, 'onMolfilePathChanged');
	this.ui.widgets.fMolfileToolButton.clicked.connect(this, 'onMolfileToolButtonClicked');
	this.ui.widgets.fShiftAndJsTableWidget.itemChanged.connect(this, 'onItemChanged');
	this.ui.widgets.cbWarnings.toggled.connect(this, 'showWarnings');
	this.ui.widgets.btClearWarnings.clicked.connect(this, 'clearWarnings');
	this.fReportParser = new MultipletReporterParser();
	this.fillUI();
}

//!
WMultipletSimulatorDialog.settingsKey = "NMR/MultipletReportDialog";

//!
WMultipletSimulatorDialog.prototype = {
	//!
	fillUI : function () {
		'use strict';
		var tableRows, i, multString, widthStr, setts;

		this.ui.widgets.lbNucleus.styleSheet = 'color: black';
		this.ui.widgets.lbNucleus.text = 'Nucleus:';
		this.ui.widgets.lbFrecuency.styleSheet = 'color: black';
		this.ui.widgets.lbFrecuency.text = 'Frequency:';
		this.ui.widgets.lbSolvent.styleSheet = 'color: black';
		this.ui.widgets.lbSolvent.text = 'Solvent:';
		this.ui.widgets.fNucleusLineEdit.text = this.fReportParser.fNucleus;
		this.ui.widgets.fSolventLineEdit.text = this.fReportParser.fSolvent;
		this.ui.widgets.fMHzDoubleSpinBox.value = parseFloat(this.fReportParser.fMHz);
		this.ui.widgets.fFromDoubleSpinBox.value = parseFloat(this.fReportParser.fFrom);
		this.ui.widgets.fToDoubleSpinBox.value = parseFloat(this.fReportParser.fTo);
		this.setNumberOfPoints(this.fReportParser.fPoints);
		this.ui.widgets.fShiftAndJsTableWidget.clear();

		tableRows = [];
		for (i = 0; i < this.fReportParser.fShiftAndJs.length; ++i) {
			multString = '';
			if (this.fReportParser.fShiftAndJs[i].jList && this.fReportParser.fShiftAndJs[i].jList.length) {
				multString = this.fReportParser.fShiftAndJs[i].jList;
			}
			widthStr = '';
			if (this.fReportParser.fShiftAndJs[i].width !== undefined) {
				widthStr = this.fReportParser.fShiftAndJs[i].width.toFixed(2);
			}
			tableRows.push([{'text': this.fReportParser.fShiftAndJs[i].shift, flags: 36}, {'text': this.fReportParser.fShiftAndJs[i].nN, flags: 36}, {'text': this.fReportParser.fShiftAndJs[i].mClass, flags: 36}, {'text': multString, flags: 36}, {'text': this.fReportParser.fShiftAndJs[i].atomNumber, flags: 36}, {'text': widthStr, flags: 36}]);
		}
		this.ui.widgets.fShiftAndJsTableWidget.items = tableRows;
		this.ui.widgets.fShiftAndJsTableWidget.horzHeaderItems = [{'text': 'Shift (ppm)'}, {'text': 'NN'}, {'text': 'Class'}, {'text': 'jList'}, {'text': 'Atom'}, {'text': 'Line Width (Hz)'}];
		this.ui.widgets.fSimulateCheckBox.checked = false;
		this.ui.widgets.fSimulateCheckBox.toolTip = 'In order to use simulation, a molecule must be selected and the atoms must be assigned.';
		setts = new Settings(WMultipletSimulatorDialog.settingsKey);
		this.ui.widgets.cbWarnings.checked = setts.value("Warnings Visible", true);
		this.showWarnings();


		if (tableRows.length) {
			this.checkNumberOfAtoms();
			this.checkWarnings();

		}
	},

	checkWarnings : function () {
		'use strict';

		var i, items, outliers;

		outliers = this.getOutliers();
		items = this.ui.widgets.fShiftAndJsTableWidget.items;

		for (i = 0; i < items.length; ++i) {

			if (outliers[parseFloat(items[i][0].text)]) {

				items[i][0].foregroundColor = "red";
				items[i][1].foregroundColor = "red";
				items[i][2].foregroundColor = "red";
				items[i][3].foregroundColor = "red";
				items[i][4].foregroundColor = "red";
				items[i][5].foregroundColor = "red";
			} else {
				items[i][0].foregroundColor = "black";
				items[i][1].foregroundColor = "black";
				items[i][2].foregroundColor = "black";
				items[i][3].foregroundColor = "black";
				items[i][4].foregroundColor = "black";
				items[i][5].foregroundColor = "black";
			}
		}

		this.ui.widgets.fShiftAndJsTableWidget.items = items;

		for (i in this.fReportParser.warnings) {
			if (this.fReportParser.warnings.hasOwnProperty(i)) {
				if (i === "nucleus") {
					this.ui.widgets.lbNucleus.styleSheet = 'color: red';
					if (this.fReportParser.autocalculated) {
						this.ui.widgets.lbNucleus.text = 'Nucleus:(Detected Automatically)';
					} else {
						this.ui.widgets.lbNucleus.text = 'Nucleus:(Default)';
					}
					this.writeWarnings(this.fReportParser.warnings.nucleus);
				} else if (i === "frecuency") {
					this.ui.widgets.lbFrecuency.styleSheet = 'color: red';
					this.ui.widgets.lbFrecuency.text = 'Frequency:(Default)';
					this.writeWarnings(this.fReportParser.warnings.frecuency);
				} else if (i === "solvent") {
					this.ui.widgets.lbSolvent.styleSheet = 'color: red';
					this.ui.widgets.lbSolvent.text = 'Solvent:(Default)';
					this.writeWarnings(this.fReportParser.warnings.solvent);
				} else if (i === "outliers") {
					this.writeWarnings(this.fReportParser.warnings.outliers);
				} else if (i === "nuclides") {
					this.markWrongNumberOfAtoms();
					this.writeWarnings(this.fReportParser.warnings.nuclides);
				}
			}
		}
	},

	checkNumberOfAtoms: function () {
		"use strict";
		var numberOfAtoms, numberOfAtomsInReport;
		if (this.fReportParser.fNucleus === "1H") {

			this.fReportParser.fMolecularFormula = this.getFormulaFromMolFile();
			if (this.fReportParser.fMolecularFormula) {
				numberOfAtomsInReport = this.getNumberOfAtomsInReport();
				numberOfAtoms = this.getNumberOfAtoms("H");
				if (numberOfAtoms !== numberOfAtomsInReport) {
					this.fReportParser.warnings.nuclides = "Number of Hs found (" + numberOfAtomsInReport + ") is inconsistent with the molecule (" + numberOfAtoms + ")";
				}
			}
		}
	},

	markWrongNumberOfAtoms: function () {
		"use strict";

		var i, items = this.ui.widgets.fShiftAndJsTableWidget.items;
		for (i = 0; i < items.length; ++i) {
			items[i][1].foregroundColor = "red";
		}
		this.ui.widgets.fShiftAndJsTableWidget.items = items;


	},

	getNumberOfAtomsInReport : function () {
		"use strict";
		var i, n = 0,
			items = this.ui.widgets.fShiftAndJsTableWidget.items;
		for (i = 0; i < items.length; ++i) {
			n += parseInt(items[i][1].text, 10);
		}
		return n;
	},

	getNumberOfAtoms: function (aNucleus) {
		"use strict";

		var reg, reMatch, numberOfAtoms;

		reg = new RegExp(aNucleus + '\\d+');
		reMatch = reg.exec(this.fReportParser.fMolecularFormula)[0];
		numberOfAtoms = reMatch.replace(/\D/g, '');
		return parseInt(numberOfAtoms, 10);
	},

	getOutliers: function () {
		"use strict";
		var i, outliers = {};
		if (this.fReportParser.outliers) {
			for (i = 0; i < this.fReportParser.outliers.length; i++) {
				outliers[this.fReportParser.outliers[i]] = true;
			}
		}
		return outliers;
	},
	//!
	setReport : function (aReport) {
		'use strict';
		this.fReportParser.parseReport(aReport);
		this.fillUI();
	},

	//!
	setNumberOfPoints : function (aNumber) {
		'use strict';
		var curIndex, ii;

		curIndex = 0;
		for (ii = 1; ii < 20; ++ii) {
			if (Math.pow(2, ii) >= aNumber) {
				curIndex = ii - 1;
				break;
			}
		}
		this.ui.widgets.fPointComboBox.currentIndex = curIndex;
	},

	//!
	onReportChanged : function () {
		'use strict';
		this.clearWarnings();
		this.setReport(this.ui.widgets.fReportPlainTextEdit.plainText.toUtf8());
	},

	//!
	onMolfilePathChanged : function (aText) {
		'use strict';
		var file_exists, file = new File(aText);
		file_exists = file.exists;
		this.ui.widgets.fSimulateCheckBox.enabled = file_exists;
		if (this.ui.widgets.fReportPlainTextEdit.plainText !== "") {
			this.onReportChanged();
		}
	},

	//!
	onMolfileToolButtonClicked : function () {
		'use strict';
		var curPath, setts, filename;

		curPath = this.ui.widgets.fMolefileLineEdit.text;
		setts = new Settings(WMultipletSimulatorDialog.settingsKey);
		if (curPath.length === 0) {
			curPath = setts.value("Last Directory", Dir.home());
		}
		filename = FileDialog.getOpenFileName('*.mol;;*.*', 'Select a molfile', curPath);
		if (filename.length !== 0) {
			this.ui.widgets.fMolefileLineEdit.text = filename;
			setts.setValue("Last Directory", File.absDirPath(filename));
		}
	},

	//!
	onVerifyToolButtonClicked : function () {
		'use strict';
		var report, molfile, result;

		report = {};
		report.nucleus = this.ui.widgets.fNucleusLineEdit.text;
		report.solvent = this.ui.widgets.fSolventLineEdit.text;
		report.frequency = this.ui.widgets.fMHzDoubleSpinBox.value;
		report.predictTo = this.ui.widgets.fToDoubleSpinBox.value;
		report.predictFrom = this.ui.widgets.fFromDoubleSpinBox.value;
		report.multiplets = this.fReportParser.fShiftAndJs;
		molfile = this.readMolfile();

		result = nmr.verifyMultipletReport(report, molfile);
		if (result) {
			if (result.error) {
				MessageBox.critical(result.error);
			} else {
				MessageBox.information(JSON.stringify(result));
			}
		} else {
			MessageBox.critical("Unknown error");
		}
	},

	//!
	readMolfile : function () {
		'use strict';
		var file, content, stream;

		content = '';
		file = new File(this.ui.widgets.fMolefileLineEdit.text);
		if (file.open(File.ReadOnly)) {
			stream = new TextStream(file);
			stream.codec = "UTF-8";
			content = stream.readAll();
			stream.flush();
			file.close();
		}
		return content;
	},

	getFormulaFromMolFile : function () {
		"use strict";

		var molObject, formula, currentDocument = mainWindow.activeDocument,
			hiddenDoc;

		if (this.ui.widgets.fMolefileLineEdit.text) {
			hiddenDoc = new Document();
			Application.lockDocument(hiddenDoc);
			serialization.open(this.ui.widgets.fMolefileLineEdit.text);
			molObject = molecule.activeMolecule();
			formula = molObject.molecularFormula();
			Application.unlockDocument();
			hiddenDoc.destroy();
			return formula;
		}
	},

	//!
	exec : function () {
		'use strict';
		var retValue, items, i, shift, js, atomNumber, width;

		retValue = this.ui.exec();
		if (retValue) {
			this.fReportParser.fNucleus = this.ui.widgets.fNucleusLineEdit.text;
			this.fReportParser.fSolvent = this.ui.widgets.fSolventLineEdit.text;
			this.fReportParser.fMHz = this.ui.widgets.fMHzDoubleSpinBox.value;
			this.fReportParser.fTo = this.ui.widgets.fToDoubleSpinBox.value;
			this.fReportParser.fFrom = this.ui.widgets.fFromDoubleSpinBox.value;
			this.fReportParser.fPoints = this.ui.widgets.fPointComboBox.currentText;
			this.fReportParser.fUsePrediction = this.ui.widgets.fSimulateCheckBox.checked;
//			this.fReportParser.fShiftAndJs = [];
//			items = this.ui.widgets.fShiftAndJsTableWidget.items;
//			for (i = 0; i < items.length; ++i) {
//				shift = items[i][0].text.match(/[+\-]?(\d+(?:\.\d+)?)/g);
//				if (shift === null) {
//					shift = [];
//				}

//				js = items[i][3].text.match(/[+\-]?(\d+(?:\.\d+)?)/g);
//				if (js === null) {
//					js = [];
//				}
//				atomNumber = items[i][4].text;
//				width = items[i][5].text;
//				this.fReportParser.fShiftAndJs.push({'shift': shift, 'nN': items[i][1].text, 'mClass': items[i][2].text, 'jList': js, 'atomNumber': atomNumber, 'width': width});
//			}
			this.fReportParser.fMolfile = this.readMolfile();
			this.fReportParser.writeSettings();
		}
		return retValue;
	},

	showWarnings : function () {
		"use strict";
		var setts;
		if (this.ui.widgets.cbWarnings.checked) {
			this.ui.widgets.gbWarnings.visible = true;
		} else {
			this.ui.widgets.gbWarnings.visible = false;
		}
		setts = new Settings(WMultipletSimulatorDialog.settingsKey);
		setts.setValue("Warnings Visible", this.ui.widgets.cbWarnings.checked);
	},

	clearWarnings : function () {
		"use strict";
		this.ui.widgets.teWarnings.plainText = "";
	},

	writeWarnings : function (aMessage) {
		"use strict";

		this.ui.widgets.teWarnings.plainText = aMessage + "\n" + this.ui.widgets.teWarnings.plainText;
	},

	//!
	onItemChanged : function (aItem) {
		'use strict';
		var col, file;
		col = aItem.column;
		if (col === 4) { //atom
			if (aItem.text === '') {
				this.ui.widgets.fSimulateCheckBox.checked = false;
				this.ui.widgets.fSimulateCheckBox.enabled = false;
			} else {
				file = new File(this.ui.widgets.fMolefileLineEdit.text);
				if (file.exists) {
					this.ui.widgets.fSimulateCheckBox.enabled = true;
				} else {
					this.ui.widgets.fSimulateCheckBox.enabled = false;
				}
				this.ui.widgets.fSimulateCheckBox.checked = false;
			}
		}
	}
};

if (this.MnUi && MnUi.scripts_nmr) {
	MnUi.scripts_nmr.scripts_nmr_ParseMultipletReport = multipletReportParser;
}
